package com.zycoin.pagehelp.mybatis.dialect;

import com.zycoin.pagehelp.mybatis.Dialect;
import com.zycoin.pagehelp.mybatis.Page;
import com.zycoin.pagehelp.mybatis.PageHelpParam;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.JdbcType;
import org.apache.ibatis.type.TypeHandlerRegistry;

import java.util.LinkedList;
import java.util.List;

/**
 * Sybase数据库方言
 * 由于Sybase对SQL分页支持不友好，这里调用存储过程来实现分页查询
 * 需要将查询sql换为调用存储过程的sql，且将原sql的参数替换到原sql中，且重新设置参数
 */
public class SybaseDialect extends Dialect {
    @Override
    public String getPageSql(Page<?> page, String sql) {
        return sql;
    }

    @Override
    public void processDialectPageInfo(MetaObject metaStatementHandler, String sql, Page<?> page) {
        Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");
        BoundSql boundSql1 = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        /*将原sql参数填充*/
        sql = parseSql(sql, configuration, boundSql1, metaStatementHandler);
        /*生成存储过程参数*/
        PageHelpParam pageHelpParam = new PageHelpParam(sql, page.getOffset(), page.getPageSize());
        /*生成查询存储过程的BoundSql*/
        BoundSql boundSql = this.getPageBoundSql(configuration, pageHelpParam);
        /*替换参数*/
        metaStatementHandler.setValue("delegate.parameterHandler.parameterObject", pageHelpParam);
        /*替换为查询存储过程的BoundSql*/
        metaStatementHandler.setValue("delegate.boundSql", boundSql);
        metaStatementHandler.setValue("delegate.parameterHandler.boundSql", boundSql);

    }

    /**
     * 生成查询存储过程的BoundSql
     *
     * @param configuration
     * @param pageHelpParam
     * @return
     */
    private BoundSql getPageBoundSql(Configuration configuration, PageHelpParam pageHelpParam) {
        List<ParameterMapping> list = new LinkedList<ParameterMapping>();
        ParameterMapping sqlP = new ParameterMapping.Builder(configuration, "sql", String.class).build();
        ParameterMapping startP = new ParameterMapping.Builder(configuration, "start", Integer.class).build();
        ParameterMapping limitP = new ParameterMapping.Builder(configuration, "limit", Integer.class).build();
        list.add(sqlP);
        list.add(startP);
        list.add(limitP);
        BoundSql buBoundSql1 = new BoundSql(configuration, "{call QueryPageList(?,?,?)}", list, pageHelpParam);
        return buBoundSql1;
    }


    private String parseSql(String sql, Configuration configuration, BoundSql boundSql, MetaObject metaStatementHandler) {
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings != null) {
            Object params = metaStatementHandler.getValue("delegate.boundSql.parameterObject");
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    if (boundSql.hasAdditionalParameter(propertyName))
                        value = boundSql.getAdditionalParameter(propertyName);
                    else if (typeHandlerRegistry.hasTypeHandler(params.getClass()))
                        value = params;
                    else {
                        MetaObject metaObject = configuration.newMetaObject(params);
                        value = metaObject.getValue(propertyName);
                    }
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    sql = replaceParameter(sql, value, jdbcType, parameterMapping.getJavaType());
                }
            }
        }
        return sql;
    }


    private String replaceParameter(String sql, Object value, JdbcType jdbcType, Class javaType) {
        String strValue = String.valueOf(value);
        if (jdbcType != null) {
            switch (jdbcType) {
                //数字
                case BIT:
                case TINYINT:
                case SMALLINT:
                case INTEGER:
                case BIGINT:
                case FLOAT:
                case REAL:
                case DOUBLE:
                case NUMERIC:
                case DECIMAL:
                    break;
                //日期
                case DATE:
                case TIME:
                case TIMESTAMP:
                    //其他，包含字符串和其他特殊类型
                default:
                    strValue = "'" + strValue + "'";
            }
        } else if (Number.class.isAssignableFrom(javaType)) {
            //不加单引号
        } else {
            strValue = "'" + strValue + "'";
        }
        return sql.replaceFirst("\\?", strValue);
    }
}
